/*******************************************************************************
 * Copyright 2011 Towee.net
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *******************************************************************************/

package net.towee.server;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.List;
import java.util.Set;

import javax.servlet.Filter;
import javax.servlet.Servlet;

import net.towee.model.utils.Pair;
import net.towee.server.util.NetUtils;

import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.nio.SelectChannelConnector;
import org.eclipse.jetty.servlet.DefaultServlet;
import org.eclipse.jetty.servlet.FilterHolder;
import org.eclipse.jetty.servlet.FilterMapping;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.inject.Inject;
import com.google.inject.name.Named;

/**
 * ServerRpcProvider can provide instances of type Service over an incoming
 * network socket and service incoming RPCs to these services and their methods.
 */
public class ServerRpcProvider {
	private static final Logger LOG = LoggerFactory.getLogger(ServerRpcProvider.class);

	private final InetSocketAddress[] httpAddresses;
	private final org.eclipse.jetty.server.SessionManager jettySessionManager;
	private Server httpServer = null;

	/**
	 * Construct a new ServerRpcProvider, hosting on the specified WebSocket
	 * addresses.
	 * 
	 * Also accepts an ExecutorService for spawning managing threads.
	 */
	public ServerRpcProvider(InetSocketAddress[] httpAddresses,
			org.eclipse.jetty.server.SessionManager jettySessionManager) {
		this.httpAddresses = httpAddresses;
		this.jettySessionManager = jettySessionManager;
	}

	@Inject
	public ServerRpcProvider(@Named(CoreSettings.HTTP_FRONTEND_ADDRESSES) List<String> httpAddresses,
			org.eclipse.jetty.server.SessionManager jettySessionManager) {
		this(parseAddressList(httpAddresses), jettySessionManager);
	}

	public void startServer() {
		httpServer = new Server();

		// Set server connectors
		List<SelectChannelConnector> connectors = getSelectChannelConnectors(httpAddresses);
		if (connectors.isEmpty()) {
			LOG.error("No valid http end point address provided!");
		}
		for (SelectChannelConnector connector : connectors) {
			httpServer.addConnector(connector);
		}

		// Create server context
		ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
		if (jettySessionManager != null) {
			context.getSessionHandler().setSessionManager(jettySessionManager);
		}
		context.setResourceBase("./war");

		// Serve the static content and GWT web client with the default servlet
		// (acts like a standard file-based web server).
		ServletHolder defaultServlet = new ServletHolder(new DefaultServlet());
		context.addServlet(defaultServlet, "/static/*");
		context.addServlet(defaultServlet, "/ui/*");

		// Set filters
		for (Pair<String, FilterHolder> servlet : filterRegistry) {
			context.addFilter(servlet.getSecond(), servlet.getFirst(), FilterMapping.REQUEST);
		}

		// Set servlets
		for (Pair<String, ServletHolder> servlet : servletRegistry) {
			context.addServlet(servlet.getSecond(), servlet.getFirst());
		}

		// Set context
		httpServer.setHandler(context);

		// Start the server
		try {
			httpServer.start();
		} catch (Exception e) { // yes, .start() throws "Exception"
			LOG.error("Fatal error starting http server.", e);
			return;
		}

		LOG.info("Towee server running.");
	}

	private static InetSocketAddress[] parseAddressList(List<String> addressList) {
		if (addressList == null || addressList.size() == 0) {
			return new InetSocketAddress[0];
		} else {
			Set<InetSocketAddress> addresses = Sets.newHashSet();
			for (String str : addressList) {
				if (str.length() == 0) {
					LOG.warn("Encountered empty address in http addresses list.");
				} else {
					try {
						InetSocketAddress address = NetUtils.parseHttpAddress(str);
						if (!addresses.contains(address)) {
							addresses.add(address);
						} else {
							LOG.warn("Ignoring duplicate address in http addresses list: Duplicate entry '"
									+ str + "' resolved to " + address.getAddress().getHostAddress());
						}
					} catch (IOException e) {
						LOG.error("Unable to process address " + str, e);
					}
				}
			}
			return addresses.toArray(new InetSocketAddress[0]);
		}
	}

	/**
	 * @return a list of {@link SelectChannelConnector} each bound to a
	 *         host:port pair form the list addresses.
	 */
	private List<SelectChannelConnector> getSelectChannelConnectors(InetSocketAddress[] httpAddresses) {
		List<SelectChannelConnector> list = Lists.newArrayList();
		for (InetSocketAddress address : httpAddresses) {
			SelectChannelConnector connector = new SelectChannelConnector();

			connector.setHost(address.getAddress().getHostAddress());
			connector.setPort(address.getPort());
			list.add(connector);
		}

		return list;
	}

	/**
	 * Stops this server.
	 */
	public void stopServer() throws IOException {
		try {
			httpServer.stop(); // yes, .stop() throws "Exception"
		} catch (Exception e) {
			LOG.warn("Fatal error stopping http server.", e);
		}
		LOG.info("server shutdown.");
	}

	/**
	 * Set of servlets
	 */
	List<Pair<String, ServletHolder>> servletRegistry = Lists.newArrayList();
	List<Pair<String, FilterHolder>> filterRegistry = Lists.newArrayList();

	/**
	 * Add a servlet to the servlet registry. This servlet will be attached to
	 * the specified URL pattern when the server is started up.
	 * 
	 * @param urlPattern
	 *            URL pattern for paths. Eg, '/foo', '/foo/*'
	 * @param servlet
	 *            The servlet object to bind to the specified paths
	 * @return the {@link ServletHolder} that holds the servlet.
	 */
	public ServletHolder addServlet(String urlPattern, Servlet servlet) {
		ServletHolder servletHolder = new ServletHolder(servlet);
		servletRegistry.add(new Pair<String, ServletHolder>(urlPattern, servletHolder));
		return servletHolder;
	}

	public FilterHolder addFilter(String string, Filter filter) {
		FilterHolder filterHolder = new FilterHolder(filter);
		filterRegistry.add(new Pair<String, FilterHolder>(string, filterHolder));
		return filterHolder;
	}
}
